package com.fable.sp.core.config;

import com.fable.sp.core.Application;
import com.google.common.collect.Sets;
import com.jolbox.bonecp.BoneCPDataSource;
import com.slyak.spring.jpa.FreemarkerSqlTemplates;
import com.slyak.spring.jpa.GenericJpaRepositoryFactoryBean;
import com.slyak.spring.jpa.GenericJpaRepositoryImpl;
import org.hibernate.cfg.Environment;
import org.hibernate.jpa.AvailableSettings;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.guava.GuavaCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Properties;
import java.util.Set;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "**.repository", repositoryBaseClass = GenericJpaRepositoryImpl.class, repositoryFactoryBeanClass = GenericJpaRepositoryFactoryBean.class, transactionManagerRef = "spTxManager")
@EnableJpaAuditing
//@EnableCaching
class JpaConfig {

    @Value("${dataSource.driverClassName}")
    private String driver;
    @Value("${dataSource.url}")
    private String url;
    @Value("${dataSource.username}")
    private String username;
    @Value("${dataSource.password}")
    private String password;

    @Value("${hibernate.hbm2ddl.auto}")
    private String hbm2ddlAuto = "update";
    @Value("${hibernate.dialect:}")
    private String dialect;
    @Value("${hibernate.show.sql:true}")
    private boolean showSql = false;

    @Value("${jpa.basepackages:}")
    private String jpaBasePackages;

    @Bean(destroyMethod = "close")
    @Conditional(MissingBeanCondition.class)
    public DataSource dataSource() {
        BoneCPDataSource dataSource = new BoneCPDataSource();
        dataSource.setDriverClass(driver);
        dataSource.setJdbcUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    @Conditional(MissingBeanCondition.class)
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, BeanFactory beanFactory) {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource);
        entityManagerFactoryBean.setBeanFactory(beanFactory);

        Set<String> pkgs = Sets.newHashSet();
        pkgs.add(ClassUtils.getPackageName(Application.class));
        if (StringUtils.hasText(jpaBasePackages)) {
            String[] split = org.apache.commons.lang3.StringUtils.split(jpaBasePackages, ",");
            if (split != null) {
                for (String pkg : split) {
                    String pkgTrim = org.apache.commons.lang3.StringUtils.trimToNull(pkg);
                    if (pkgTrim != null) {
                        pkgs.add(pkgTrim);
                    }
                }
            }
        }

        entityManagerFactoryBean.setPackagesToScan(pkgs.toArray(new String[pkgs.size()]));
        entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());

        Properties jpaProperties = new Properties();
        jpaProperties.put(org.hibernate.cfg.Environment.DIALECT, guessDialect());
        jpaProperties.put(org.hibernate.cfg.Environment.HBM2DDL_AUTO, hbm2ddlAuto);
        jpaProperties.put(AvailableSettings.NAMING_STRATEGY, "org.hibernate.cfg.ImprovedNamingStrategy");
        jpaProperties.put(Environment.SHOW_SQL, showSql);
        entityManagerFactoryBean.setJpaProperties(jpaProperties);

        return entityManagerFactoryBean;
    }

    private String guessDialect() {
        int occur = 0;
        String dialectTmp = null;
        if (ClassUtils.isPresent("com.mysql.jdbc.Driver", getClass().getClassLoader())) {
            occur++;
            dialectTmp = "org.hibernate.dialect.MySQL5InnoDBDialect";
        }
        if (ClassUtils.isPresent("org.hsqldb.jdbcDriver", getClass().getClassLoader())) {
            occur++;
            dialectTmp = "org.hibernate.dialect.HSQLDialect";
        }
        if (ClassUtils.isPresent("oracle.jdbc.OracleDriver", getClass().getClassLoader())) {
            occur++;
            dialectTmp = "org.hibernate.dialect.OracleDialect";
        }
        if (occur == 1) {
            return dialectTmp;
        }

        //multi drivers, so use config form smart-page.properties
        if (StringUtils.isEmpty(dialect)) {
            throw new RuntimeException("${hibernate.dialect} must be set at classpath:smart-page.properties.");
        }
        return dialect;
    }

    @Bean(name = "spTxManager")
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory, BeanFactory beanFactory) {
        JpaTransactionManager jpaTransactionManager = new JpaTransactionManager(entityManagerFactory);
        jpaTransactionManager.setBeanFactory(beanFactory);
        return jpaTransactionManager;
    }

    @Bean
    public FreemarkerSqlTemplates freemarkerSqlTemplates() {
        FreemarkerSqlTemplates templates = new FreemarkerSqlTemplates();
        templates.setTemplateLocation("classpath:/sqls");
        return templates;
    }

    @Bean(name = "spCacheManager")
    public CacheManager cacheManager() {
        return new GuavaCacheManager();
    }
}
